﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace GbopSdk
{
    public class GbopHelper
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="exeType">cmd;restsharp;http</param>
        /// <returns></returns>
        public static GbopResponse ExeHttp(GbopRequest request)
        {
            Stopwatch watch = Stopwatch.StartNew();
            watch.Start();
            string signStr = string.Empty;
            HttpClient httpClient = new HttpClient();
            try
            {
                request.RequestTime = DateTime.Now;
                request.SignedHeaderDic = InitialBasicHeader(request, out signStr);
                request.Url = InitUrl(request);
                httpClient.Timeout = TimeSpan.FromMilliseconds(60000);
                httpClient.BaseAddress = new Uri(request.Url);
                httpClient.DefaultRequestHeaders.Clear();
                foreach (KeyValuePair<string, string> kv in request.SignedHeaderDic)
                {
                    httpClient.DefaultRequestHeaders.TryAddWithoutValidation(kv.Key, Utf8ToIso88591(kv.Value));
                }


                string result = "";
                try
                {
                    //string json = GetBodyJsonString(request.Bodys ?? new List<BodyItem>());
                    string json = string.IsNullOrEmpty(request.ContentJson) ? GbopHelper.GetBodyJsonString(request.Bodys ?? new List<BodyItem>()) : request.ContentJson;
                    HttpContent content = null;
                    if (json.Length > 0)
                    {
                        content = new StringContent(json, Encoding.UTF8, "application/json");
                    }

                    switch (request.Method)
                    {
                        case GbopHttpMethod.GET:
                            {
                                var response = httpClient.GetAsync(request.Url).ConfigureAwait(false);
                                result = response.GetAwaiter().GetResult().Content.ReadAsStringAsync().Result;
                            }
                            break;
                        case GbopHttpMethod.POST:
                            {
                                var response = httpClient.PostAsync(request.Url, content).ConfigureAwait(false);
                                result = response.GetAwaiter().GetResult().Content.ReadAsStringAsync().Result;
                            }
                            break;
                        case GbopHttpMethod.PUT:
                            {
                                var response = httpClient.PutAsync(request.Url, content).ConfigureAwait(false);
                                result = response.GetAwaiter().GetResult().Content.ReadAsStringAsync().Result;
                            }
                            break;
                        case GbopHttpMethod.DELETE:
                            {
                                var response = httpClient.DeleteAsync(request.Url).ConfigureAwait(false);
                                result = response.GetAwaiter().GetResult().Content.ReadAsStringAsync().Result;
                            }
                            break;
                        case GbopHttpMethod.PATCH:
                            {
                                HttpRequestMessage req = new HttpRequestMessage()
                                {
                                    Method = new HttpMethod("PATCH"),
                                    Content = content,
                                    RequestUri = httpClient.BaseAddress
                                };
                                var response = httpClient.SendAsync(req).ConfigureAwait(false);
                                result = response.GetAwaiter().GetResult().Content.ReadAsStringAsync().Result;
                            }
                            break;
                    }
                    watch.Stop();
                    return new GbopResponse(signStr, httpClient.DefaultRequestHeaders, result)
                    {
                        RequestItem = request,
                        RequestException = null,
                        IsSuccess = true,
                        ElapsedMilliseconds = watch.ElapsedMilliseconds,
                        ResponseMethod = "HttpClient"
                    };
                }
                catch (Exception ex)
                {
                    watch.Stop();
                    return new GbopResponse(signStr, httpClient.DefaultRequestHeaders, ex.Message + GbopConsts.LF + ex.InnerException?.Message)
                    {
                        RequestItem = request,
                        RequestException = ex,
                        IsSuccess = false,
                        ElapsedMilliseconds = watch.ElapsedMilliseconds
                    };
                }
            }
            catch (Exception ex)
            {
                watch.Stop();
                return new GbopResponse(signStr, httpClient.DefaultRequestHeaders, ex.Message + GbopConsts.LF + ex.InnerException?.Message)
                {
                    RequestItem = request,
                    RequestException = ex,
                    IsSuccess = false,
                    ElapsedMilliseconds = watch.ElapsedMilliseconds
                };
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bodys"></param>
        /// <returns></returns>
        private static string GetBodyJsonString(List<BodyItem> bodys)
        {
            StringBuilder sb = new StringBuilder();
            if (bodys.Count > 0)
                sb.Append("{");
            foreach (BodyItem bi in bodys)
            {
                sb.Append($"\"{bi.BodyKey}\":\"{bi.BodyValue}\",");
            }
            if (sb.Length > 0 && bodys.Count > 0)
            {
                return sb.ToString().Trim(',') + "}";
            }
            else
            {
                return sb.ToString();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        internal static string GetCurArgInfo(GbopRequest request)
        {
            StringBuilder sb = new StringBuilder();
            try
            {
                sb.Append("\"").Append(request.Url).Append("\"");
                sb.Append(" -X ").Append(request?.Method);
                foreach (var i in request.SignedHeaderDic)
                {
                    sb.Append($" -H \"{i.Key}:{i.Value}\"");
                }
                sb.Append(GbopConsts.LF);
            }
            catch
            {

            }
            return sb.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        internal static string GetCurArgInfo(GbopResponse response)
        {
            StringBuilder sb = new StringBuilder();
            try
            {
                sb.Append("\"").Append(response.RequestItem.Url).Append("\"");
                sb.Append(" -X ").Append(response?.RequestItem?.Method);
                foreach (var i in response.RequestHeaders)
                {
                    sb.Append($" -H \"{i.Key}:{string.Concat(i.Value)}\"");
                }
                sb.Append(GbopConsts.LF);
            }
            catch
            {

            }
            return sb.ToString();
        }

        /// <summary
        /// 打开控制台执行拼接完成的批处理命令字符串
        /// </summary>
        /// <param name="inputAction">需要执行的命令委托方法：每次调用 <paramref name="inputAction"/> 中的参数都会执行一次</param>
        /// <returns></returns>
        private static string ExecBatCommand(GbopRequest request)
        {
            string cmdText = GetCurArgInfo(request);
            string msg = "";

            Process pro;

            try
            {
                pro = new Process();
                pro.StartInfo.FileName = "CURL";
                pro.StartInfo.Arguments = cmdText;
#if NETCOREAPP3_1
                pro.StartInfo.StandardInputEncoding = Encoding.UTF8;
#endif
                pro.StartInfo.StandardOutputEncoding = Encoding.UTF8;
                pro.StartInfo.StandardErrorEncoding = Encoding.UTF8;
                pro.StartInfo.UseShellExecute = false;
                pro.StartInfo.CreateNoWindow = true;
                pro.StartInfo.RedirectStandardInput = true;
                pro.StartInfo.RedirectStandardOutput = true;
                pro.StartInfo.RedirectStandardError = true;
                pro.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null) return;
                    msg = e.Data;
                };
                pro.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null) return;
                    msg = e.Data;
                };
                pro.Start();
                pro.StandardInput.AutoFlush = true;

                pro.BeginOutputReadLine();
                //pro.StandardInput.WriteLine(cmdText);

                pro.WaitForExit();
                return msg;
            }
            finally
            {
                //if (pro != null && !pro.HasExited)
                //    pro.Kill();

                //if (pro != null)
                //    pro.Close();
            }
        }

        /// <summary>
        /// UTF-8编码转换为ISO-9959-1
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private static string Utf8ToIso88591(string str)
        {
            if (str == null)
            {
                return str;
            }
            return Encoding.GetEncoding("ISO-8859-1").GetString(Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding("ISO-8859-1"), Encoding.UTF8.GetBytes(str)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private static string InitUrl(GbopRequest request)
        {
            StringBuilder sbUrl = new StringBuilder();
            sbUrl.Append(request.HostUrl);
            if (!string.IsNullOrEmpty(request.PATH))
            {
                sbUrl.Append(request.PATH);
            }
            if (null != request.Querys)
            {
                StringBuilder sbQuery = new StringBuilder();
                foreach (var query in request.Querys)
                {
                    if (0 < sbQuery.Length)
                    {
                        sbQuery.Append("&");
                    }
                    if (string.IsNullOrEmpty(query.QueryKey) && !string.IsNullOrEmpty(query.QueryValue))
                    {
                        sbQuery.Append(query.QueryValue);
                    }
                    if (!string.IsNullOrEmpty(query.QueryKey))
                    {
                        sbQuery.Append(query.QueryKey);
                        if (!string.IsNullOrEmpty(query.QueryValue))
                        {
                            sbQuery.Append("=");

                            sbQuery.Append(UrlHelper.UrlEncode(query.QueryValue));
                        }
                    }
                }
                if (0 < sbQuery.Length)
                {
                    sbUrl.Append("?").Append(sbQuery);
                }
            }

            return sbUrl.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="signStr"></param>
        /// <returns></returns>
        private static Dictionary<string, string> InitialBasicHeader(GbopRequest request, out string signStr)
        {
            Dictionary<string, string> headers = request.Headers.Where(i => i.HeaderCheck).ToDictionary(i => i.HeaderKey, i => i.HeaderValue);

            //添加网关需要的header
            //headers.Add(X_GCA_TIMESTAMP, "1603759556674");
            string time = ConvertDateTimeInt(request.RequestTime).ToString();
            headers.Add(GbopConsts.X_GCA_TIMESTAMP, time);
            headers.Add(GbopConsts.HEADER_DATE, ToGMTString(request.RequestTime));
            headers.Add(GbopConsts.X_Gapi_Sdk_Language, "csharp");
            headers.Add(GbopConsts.X_Gapi_Sdk_Version, Assembly.GetExecutingAssembly().GetName().Version.ToString());

            if (!headers.ContainsKey(GbopConsts.X_GCA_ALGORITHM))
                headers.Add(GbopConsts.X_GCA_ALGORITHM, "hmac-sha256");

            if (!headers.ContainsKey(GbopConsts.X_GCA_APP_KEY))
                headers.Add(GbopConsts.X_GCA_APP_KEY, request.AK);
            else
                headers[GbopConsts.X_GCA_APP_KEY] = request.AK;

            if (!headers.ContainsKey(GbopConsts.X_GCA_HEADERS))
                headers.Add(GbopConsts.X_GCA_HEADERS, string.Concat(GbopConsts.X_GCA_TIMESTAMP, ";", GbopConsts.HEADER_HOST));
            else
                headers[GbopConsts.X_GCA_HEADERS] = string.Concat(GbopConsts.X_GCA_TIMESTAMP, ";", GbopConsts.HEADER_HOST);

            if (!headers.ContainsKey(GbopConsts.HEADER_HOST))
                headers.Add(GbopConsts.HEADER_HOST, request.Domain);
            else
                headers[GbopConsts.HEADER_HOST] = request.Domain;

            headers.Add(GbopConsts.X_GCA_SIGNATURE, Sign(request, headers, out signStr));

            foreach (var item in request.Headers.Where(i => !i.HeaderCheck))
            {
                if (!headers.ContainsKey(item.HeaderKey))
                    headers.Add(item.HeaderKey, item.HeaderValue);
            }

            return headers;
        }

        /// <summary>
        /// DateTime时间格式转换为Unix时间戳格式
        /// </summary>
        /// <param name="time"> DateTime时间格式</param>
        /// <returns>Unix时间戳格式</returns>
        private static long ConvertDateTimeInt(DateTime time)
        {
            DateTime startTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local);
            return (long)(time - startTime).TotalMilliseconds;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        private static DateTime ConvertIntDateTime(long time)
        {
            DateTime startTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local);
            return startTime.AddMilliseconds(time);
        }

        /// <summary>
        /// 本地时间转成GMT时间
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        private static string ToGMTString(DateTime dt)
        {
            return dt.ToUniversalTime().ToString("r");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="headers"></param>
        /// <param name="tosign"></param>
        /// <returns></returns>
        private static string Sign(GbopRequest request, Dictionary<string, string> headers, out string tosign)
        {
            try
            {
                tosign = BuildStringToSign(request, headers);
                return CreateToken(tosign, request.SK);
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <param name="secret"></param>
        /// <returns></returns>
        private static string CreateToken(string message, string secret)
        {
            secret = secret ?? "";
            var encoding = new System.Text.UTF8Encoding();
            byte[] keyByte = encoding.GetBytes(secret);
            byte[] messageBytes = encoding.GetBytes(message);
            using (var hmacsha256 = new HMACSHA256(keyByte))
            {
                byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                return Convert.ToBase64String(hashmessage);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
        private static string BuildStringToSign(GbopRequest request, Dictionary<string, string> headers)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(request.Method.ToString().ToUpper()).Append(GbopConsts.LF);
            sb.Append(request.PATH).Append(GbopConsts.LF);
            string qs = QueryStringSign(request);
            sb.Append(qs).Append(GbopConsts.LF);
            if (null != headers)
            {
                if (headers.ContainsKey(GbopConsts.X_GCA_APP_KEY))
                {
                    sb.Append(headers[GbopConsts.X_GCA_APP_KEY]);
                }
                sb.Append(GbopConsts.LF);
                if (headers.ContainsKey(GbopConsts.HEADER_DATE))
                {
                    sb.Append(headers[GbopConsts.HEADER_DATE]);
                }
                sb.Append(GbopConsts.LF);
                if (headers.ContainsKey(GbopConsts.X_GCA_HEADERS))
                {
                    string custHeaders = headers[GbopConsts.X_GCA_HEADERS];
                    String[] header = custHeaders.Split(';');
                    foreach (string item in header)
                    {
                        sb.Append(item).Append(":");
                        if (headers.ContainsKey(item))
                        {
                            sb.Append(headers[item]);
                        }
                        sb.Append(GbopConsts.LF);
                    }
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="querysDic"></param>
        /// <returns></returns>
        private static string QueryStringSign(GbopRequest request)
        {
            StringBuilder sb = new StringBuilder();

            StringBuilder sbParam = new StringBuilder();
            foreach (var item in request.Querys.OrderBy(i => i.QueryKey + i.QueryValue))
            {
                if (!string.IsNullOrEmpty(item.QueryKey))
                {
                    if (0 < sbParam.Length)
                    {
                        sbParam.Append("&");
                    }
                    sbParam.Append(item.QueryKey).Append("=");
                    if (!string.IsNullOrEmpty(item.QueryValue))
                    {
                        if (request.EnableUrlEncode || item.EnableQueryUrlEncode)
                        {
                            //string str = HttpUtility.UrlEncode(item.Value, System.Text.Encoding.UTF8);
                            sbParam.Append(UrlHelper.UrlEncode(item.QueryValue));
                        }
                        else
                        {
                            sbParam.Append(item.QueryValue);
                        }
                    }
                }
            }
            if (0 < sbParam.Length)
            {
                sb.Append(sbParam);
            }
            return sb.ToString();
        }
    }
}
